//
//  MFAPMNetMonitorURLProtocol.m
//  MFAPMManager
//
//  Created by zhang liyu on 2019/5/22.
//

#import "MFAPMNetMonitorURLProtocol.h"

#import <AFNetworking/AFNetworkReachabilityManager.h>

#import "MFAPMNetMonitorURLSession.h"
#import "NSObject+MFAPMSwizzle.h"
#import "MFAPMNetMonitorModel.h"
#import "MFAPMNetAOPProtocol.h"
#import "MFAPMUploadManager.h"
#import "MFAPMManager.h"
#import "MFAPMUtility.h"
#import "MFAPMConfig.h"
#import <objc/runtime.h>

static NSString * const kMFAPMNetMonitorProtocolKey = @"kMFAPMNetMonitorProtocolKey";

@interface MFAPMNetMonitorURLProtocol() <NSURLSessionDataDelegate>

@property (nonatomic, strong) NSURLSessionDataTask *dataTask;
@property (nonatomic, strong) MFAPMNetMonitorModel *netMonitorModel;
//当前Runloop的Modes数组
@property (atomic, copy) NSArray *modesArr;

/**
 是否已经开启对AFNetwork等第三方的网络请求监听
 
 YES 已开启
 NO  未开启
 默认为NO
 */
@property (nonatomic, assign) BOOL isStartAFNetMonitor;

@end

@implementation MFAPMNetMonitorURLProtocol

#pragma mark - Singleton

+ (instancetype)sharedInstance{
    static MFAPMNetMonitorURLProtocol *protocol = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        protocol = [[MFAPMNetMonitorURLProtocol alloc] init];
    });
    return protocol;
}

+ (MFAPMNetMonitorURLSession *)mfapmSharedURLSession{
    static MFAPMNetMonitorURLSession *urlSession = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
        config.protocolClasses = @[[MFAPMNetMonitorURLProtocol class]];

        urlSession = [[MFAPMNetMonitorURLSession alloc] initWithConfiguration:config];
    });
    return urlSession;
}

#pragma mark - Life Circle

- (instancetype)init{
    self = [super init];
    if(self){
        [self configActions];
    }
    return self;
}

- (void)configActions{
    _isStartAFNetMonitor = NO;
}

#pragma mark - Monitor Control Actions

- (void)startMonitor{
    [[AFNetworkReachabilityManager sharedManager] startMonitoring];
    [NSURLProtocol registerClass:MFAPMNetMonitorURLProtocol.class];
    [self startAFNetRequestMonitor];
}

- (void)stopMonitor{
    //停止监听时，关闭当前请求URLSession
    [[MFAPMNetMonitorURLProtocol mfapmSharedURLSession].session finishTasksAndInvalidate];
    [NSURLProtocol unregisterClass:MFAPMNetMonitorURLProtocol.class];
    [self stopAFNetRequestMonitor];
}

#pragma mark - AFNetworking Monitor Controll Actions

- (void)startAFNetRequestMonitor{
    Class urlSessionClass = NSClassFromString(@"__NSCFURLSessionConfiguration") ? : NSClassFromString(@"NSURLSessionConfiguration");
    self.isStartAFNetMonitor = [urlSessionClass mfapm_swizzleSEL:@selector(protocolClasses) fromClass:urlSessionClass toClass:[self class]];
}

- (void)stopAFNetRequestMonitor{
    //当开启了监听时，进行监听的移除操作
    if(self.isStartAFNetMonitor){
        self.isStartAFNetMonitor = NO;
        //将方法交换回来
        Class urlSessionClass = NSClassFromString(@"__NSCFURLSessionConfiguration") ? : NSClassFromString(@"NSURLSessionConfiguration");
        [urlSessionClass mfapm_swizzleSEL:@selector(protocolClasses) fromClass:urlSessionClass toClass:[self class]];
    }
}

- (NSArray *)protocolClasses{
    return @[[MFAPMNetMonitorURLProtocol class]];
}

#pragma mark - NetMonitor Delegate Actions

/**
 需要监控的请求
 
 @param request 本次的请求
 @return 是否需要被监控
 */
+ (BOOL)canInitWithRequest:(NSURLRequest *)request{
    
    //开关关闭监控，不需要进行监控
    if (![MFAPMManager sharedManager].isMonitorEnabled) {
        return NO;
    }
    
    //对空请求不进行拦截
    if(!request || !request.URL){
        return NO;
    }
    
    //当前请求为图片请求，暂时不做拦截处理
    NSString *pathExtension = [request.URL.absoluteString pathExtension];
    if([pathExtension isEqualToString:@"jpg"]
       || [pathExtension isEqualToString:@"png"]){
        return NO;
    }
    
    //如果是已经拦截过的就放行，避免出现死循环
    if ([NSURLProtocol propertyForKey:kMFAPMNetMonitorProtocolKey inRequest:request]) {
        return NO;
    }
    
    //只拦截http和htpps的请求，TODO:根据mineType进行拦截
    NSString *scheme = request.URL.scheme;
    if (![scheme isEqualToString:@"http"]
        && ![scheme isEqualToString:@"https"]) {
        return NO;
    }
    
    //如果设置了全局拦截的白名单，则只拦截白名单内的域名的请求
    NSArray *whiteHostList = [MFAPMManager sharedManager].config.netMonitorWhiteHostArr;
    NSString *host = request.URL.host;
    if (whiteHostList.count > 0 && host.length > 0) {
        NSString *forepart = [NSString stringWithFormat:@"%@://%@", scheme, host];
        if (![whiteHostList containsObject:forepart]) {
            return NO;
        }
    }
    return YES;
}

+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request{
    
    NSMutableURLRequest *mutableReqeust = [request mutableCopy];
    
    //处理session拦截请求body丢失
    if(mutableReqeust.HTTPBody.length == 0){
        mutableReqeust.HTTPBody = [MFAPMUtility getHttpBodyFromRequest:request];
    }
    [MFAPMNetMonitorURLProtocol setProperty:@YES
                                     forKey:kMFAPMNetMonitorProtocolKey
                                  inRequest:mutableReqeust];
    
    return mutableReqeust;
}

+ (BOOL)requestIsCacheEquivalent:(NSURLRequest *)a toRequest:(NSURLRequest *)b{
    return [super requestIsCacheEquivalent:a toRequest:b];
}

- (void)startLoading {
    
    //初始化RunloopModes数组
    NSMutableArray *calculatedModes = [NSMutableArray arrayWithObject:NSDefaultRunLoopMode];
    //保存当前RunloopMode
    NSString *currentMode = [[NSRunLoop currentRunLoop] currentMode];
    if ((currentMode != nil) && ![currentMode isEqual:NSDefaultRunLoopMode]) {
        [calculatedModes addObject:currentMode];
    }
    //保存RunloopMode数组
    self.modesArr = [NSArray arrayWithArray:calculatedModes];
    
    //初始化请求任务
    self.dataTask = [[[self class] mfapmSharedURLSession] dataTaskWithRequest:[MFAPMNetMonitorURLProtocol canonicalRequestForRequest:self.request] delegate:self modes:self.modesArr];
    [self.dataTask resume];
    
    self.netMonitorModel = [[MFAPMNetMonitorModel alloc] init];
    [self.netMonitorModel updateRequestData:self.request];

    NSArray *interceptorsArr = [[MFAPMManager sharedManager].config fetchNetMonitorURLProtocolInterceptors];
    [interceptorsArr enumerateObjectsUsingBlock:^(id <MFAPMNetAOPProtocol> _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        if ([obj respondsToSelector:@selector(startLoading:)]) {
            [obj startLoading:self.netMonitorModel];
        }
    }];
}

- (void)stopLoading {
    //在请求结束后，停止URLSession，使得代理对象能够释放
    if(self.dataTask != nil){
        [self.dataTask cancel];
        self.dataTask = nil;
    }
    
    [self.netMonitorModel finishRequest];

    //当当前code不为初始值，并且状态出错时，进行对应事件上报处理
    if(self.netMonitorModel.responseMessage.length > 0
       ||(self.netMonitorModel.statusCode.length > 0
          && ![self.netMonitorModel.statusCode isEqualToString:@"200"])){
        //有错误时或者请求状态码返回不为200，进行网络事件的缓存操作
        [MFAPMUploadManager cacheItem:self.netMonitorModel];
    }else if (self.netMonitorModel.responseCode.length > 0
              &&![self.netMonitorModel.responseCode isEqualToString:@"0"]){
        //当服务器返回的数据Code不为0时，立即进行事件上报操作
        [MFAPMUploadManager uploadCacheDataNowWithItem:self.netMonitorModel];
    }
    
    NSArray *interceptorsArr = [[MFAPMManager sharedManager].config fetchNetMonitorURLProtocolInterceptors];
    [interceptorsArr enumerateObjectsUsingBlock:^(id <MFAPMNetAOPProtocol> _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        if ([obj respondsToSelector:@selector(stopLoading:)]) {
            [obj stopLoading:self.netMonitorModel];
        }
    }];
//    这些坑包括：https的证书校验，NSURLConnection和NSURLSession兼容,超时处理，各种异常处理（不能因为你崩了让程序跟着崩了)
}

#pragma mark - NSURLSessionTaskDelegate

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
    if (!error) {
        [self.client URLProtocolDidFinishLoading:self];
    } else if ([error.domain isEqualToString:NSURLErrorDomain] && error.code == NSURLErrorCancelled) {
        //请求取消
    } else {
        //错误
        [self.client URLProtocol:self didFailWithError:error];
        [self.netMonitorModel updateErrorData:error];
    }
    //收到结束消息，直接停止当前的任务
    [self.dataTask cancel];
    self.dataTask = nil;
}

#pragma mark - NSURLSessionDataDelegate

- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler {
    //允许当前缓存请求数据
    [self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageAllowed];
    completionHandler(NSURLSessionResponseAllow);
    
    [self.netMonitorModel updateResponseData:(NSHTTPURLResponse *)response];
}

// 当服务端返回信息时，这个回调函数会被ULS调用，在这里实现http返回信息的截取
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
    // 返回给URL Loading System接收到的数据
    [self.client URLProtocol:self didLoadData:data];
    
    [self.netMonitorModel updateReceiveData:data];
}

- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask willCacheResponse:(NSCachedURLResponse *)proposedResponse completionHandler:(void (^)(NSCachedURLResponse * _Nullable))completionHandler{
    completionHandler(proposedResponse);
}

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest * _Nullable))completionHandler {
    
    if (response != nil){
        [self.netMonitorModel updateResponseData:(NSHTTPURLResponse *)response];
        
        NSMutableURLRequest *redirectRequest = [request mutableCopy];
        
        //移除key，使其重定向请求可以被重新监听
        [MFAPMNetMonitorURLProtocol removePropertyForKey:kMFAPMNetMonitorProtocolKey inRequest:redirectRequest];
        [[self client] URLProtocol:self wasRedirectedToRequest:redirectRequest redirectResponse:response];
        
        //取消当前请求，并返回取消错误进行后续处理
        [self.task cancel];
        [[self client] URLProtocol:self didFailWithError:[NSError errorWithDomain:NSCocoaErrorDomain code:NSUserCancelledError userInfo:nil]];
    }
}

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler{
    
    //暂时注释，因为传递消息到拼拼客户端会引起客户端对应函数代码崩溃，本身代码没有问题
//    [self.client URLProtocol:self didReceiveAuthenticationChallenge:challenge];
    
    //默认信任服务器
    if (challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust) {
        NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
        completionHandler(NSURLSessionAuthChallengeUseCredential,credential);
    }
}

//- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics API_AVAILABLE(ios(10.0)){
//    [[self client] URLProtocolDidFinishLoading:self];
//}

//进行认证的方法，暂时注释，需要时进行打开，并输入用户名和密码进行验证处理

//- (instancetype)initWithRequest:(NSURLRequest *)request cachedResponse:(NSCachedURLResponse *)cachedResponse client:(id<NSURLProtocolClient>)client{
//
//    NSMutableURLRequest*    redirectRequest;
//    redirectRequest = [request mutableCopy];
//
//    //添加认证信息
//    NSString *authString = [[[NSString stringWithFormat:@"%@:%@", kGlobal.userInfo.sAccount, kGlobal.userInfo.sPassword] dataUsingEncoding:NSUTF8StringEncoding] base64EncodedString];
//    authString = [NSString stringWithFormat: @"Basic %@", authString];
//    [redirectRequest setValue:authString forHTTPHeaderField:@"Authorization"];
//    NSLog(@"拦截的请求:%@",request.URL.absoluteString);
//
//    self = [super initWithRequest:redirectRequest cachedResponse:cachedResponse client:client];
//    if (self) {
//
//        // Some stuff
//    }
//    return self;
//}
//
//- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler{
//
//    NSLog(@"自定义Protocol开始认证...");
//    NSString *authMethod = [[challenge protectionSpace] authenticationMethod];
//    NSLog(@"%@认证...",authMethod);
//
//    if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
//        NSURLCredential *card = [[NSURLCredential alloc] initWithTrust:challenge.protectionSpace.serverTrust];
//        completionHandler(NSURLSessionAuthChallengeUseCredential,card);
//    }
//
//    if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodNTLM]) {
//        if ([challenge previousFailureCount] == 0) {
//            NSURLCredential *credential = [NSURLCredential credentialWithUser:kGlobal.userInfo.sAccount password:kGlobal.userInfo.sPassword persistence:NSURLCredentialPersistenceForSession];
//            [[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
//            completionHandler(NSURLSessionAuthChallengeUseCredential,credential);
//        }else{
//            completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge,nil);
//        }
//    }
//
//    NSLog(@"自定义Protocol认证结束");
//}

@end
